package stats;

import static org.junit.Assert.fail;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.List;
import java.util.Vector;

import bgai.ai.minimax.Search;
import bgai.gamelogic.Backgammon;
import bgai.gamelogic.BadMoveException;
import bgai.gamelogic.CheckerColor;
import bgai.gamelogic.Move;
import bgai.players.ComputerPlayer;
import bgai.players.ComputerPlayer.SearchType;
import bgai.players.Door;
import bgai.players.DoorHit1;
import bgai.players.DoorHit2;
import bgai.players.Hit1;
import bgai.players.Hit2;
import bgai.players.Player;
import bgai.players.Random;

public class Stats {

	private static final int NUM_RUNS= 20;
	private static final int INITIAL_SEED= 2;
	
	private static boolean SPECIAL_EXPECTIMAX= false;
	private static boolean SPECIAL_STAR1= false;
	
	public static void main(String[] args) throws BadMoveException, FileNotFoundException {
		
		if (args.length!=1) {
			System.out.println("averageNodesExpandedAndTime | SPECIAL_EXPECTIMAX | SPECIAL_STAR1 | sfidaEuristiche | sfidaProfondita");
			return;
		} else if (args[0].compareTo("averageNodesExpandedAndTime")==0) {
			
			System.out.println("averageNodesExpandedAndTime()");
			averageNodesExpandedAndTime();
			
		} else if (args[0].compareTo("SPECIAL_EXPECTIMAX")==0) {
			
			SPECIAL_EXPECTIMAX= true;
			System.out.println("averageNodesExpandedAndTime(), SPECIAL_EXPECTIMAX");
			averageNodesExpandedAndTime();
			
		} else if (args[0].compareTo("SPECIAL_STAR1")==0) {
			
			SPECIAL_STAR1= true;
			System.out.println("averageNodesExpandedAndTime(), SPECIAL_STAR1");
			averageNodesExpandedAndTime();
			
		} else if (args[0].compareTo("sfidaEuristiche")==0) {
			
			System.out.println("sfidaEuristiche()");
			sfidaEuristiche();
			
		} else if (args[0].compareTo("sfidaProfondita")==0) {
			
			System.out.println("sfidaProfondita()");
			sfidaProfondita();
			
		}
	}

	private static void averageNodesExpandedAndTime() throws BadMoveException, FileNotFoundException {
		
		PrintStream nodiEspansi= new PrintStream("nodi_espansi_VS_depth");
		PrintStream tempoEsecuzione= new PrintStream("tempo_esecuzione_VS_depth");
		
		Search.setSilentDebug(true); // <-------- output classe Search
		Search.setSilent(true); // <-------- output classe Search
		Search.DISABLE_PROBING_AB= false;
		Search.DISABLE_PROBING_CUTOFF= false;
		Search.FAIL_SOFT= false; // fail hard
		
		System.out.println("DISABLE_PROBING_AB="+Search.DISABLE_PROBING_AB);
		System.out.println("DISABLE_PROBING_CUTOFF="+Search.DISABLE_PROBING_CUTOFF);
		System.out.println("FAIL_SOFT="+Search.FAIL_SOFT);
		System.out.println();
		
		
		Vector<Double> maxNode= new Vector<Double>();
		Vector<Double> minNode= new Vector<Double>();
		Vector<Double> maxTime= new Vector<Double>();
		Vector<Double> minTime= new Vector<Double>();


		nodiEspansi.println("depth\texp\tstar1\tstar2");
		tempoEsecuzione.println("depth\texp (s)\tstar1 (s)\tstar2 (s)");
		
		SearchType arrTypes[]= {SearchType.EXPECTIMAX, SearchType.STAR1, SearchType.STAR2};
		
		final int maxDepth= 5;
		// ciclo sulla profondita'
		for (int depth=1; depth<=maxDepth; depth+=2) {

			nodiEspansi.print(depth);
			tempoEsecuzione.print(depth);
			
			// ciclo sul tipo di ricerca
			for (SearchType type: arrTypes) {
				
				maxNode.clear();
				minNode.clear();
				maxTime.clear();
				minTime.clear();
				
				System.out.println("+ + + Nuova configurazione");
				System.out.println("type="+type);
				System.out.println("depth="+depth);
				System.out.println();
				
				// XXX XXX XXX questa configuraizone e' esageratamente costosa: salto
				boolean badGuy= depth==5 && (type==SearchType.EXPECTIMAX || type==SearchType.STAR1);
				
				if (SPECIAL_EXPECTIMAX && depth==5 && type==SearchType.EXPECTIMAX) {
					// eseguire
					badGuy= false;
				} else if (SPECIAL_EXPECTIMAX) {
					// non eseguire le altre configurazioni
					badGuy= true;
				}
				
				if (SPECIAL_STAR1 && depth==5 && type==SearchType.STAR1) {
					// eseguire
					badGuy= false;
				} else if (SPECIAL_STAR1) {
					// non eseguire le altre configurazioni
					badGuy= true;
				}
				
				if (badGuy) {
					// salto
					System.out.println("skip");
					System.out.println();
					
					nodiEspansi.print("\tskip");
					tempoEsecuzione.print("\tskip");
					continue;
				}
				
				// ciclo sul numero dei run
				int seed= INITIAL_SEED;
				int num= NUM_RUNS;
				if (SPECIAL_EXPECTIMAX || SPECIAL_STAR1) num= 1;
				for (int k=0; k<num; k++) {
					
					ComputerPlayer black= new DoorHit2(CheckerColor.BLACK, depth, type);
					ComputerPlayer white= new DoorHit2(CheckerColor.WHITE, depth, type);

					System.out.println("seed="+seed);
					
					// esegue l'intera partita!!!
					playMatch(white, black, seed);

					double d;

					d= Search.meanNodes(true);
					maxNode.add(d);
					System.out.println("Media MaxNode: "+d);
					d= Search.meanTime(true)/1000.0;
					maxTime.add(d);
					System.out.println("Media MaxTime: "+d);

					d= Search.meanNodes(false);
					minNode.add(d);
					System.out.println("Media MinNode: "+d);
					d= Search.meanTime(true)/1000.0;
					minTime.add(d);
					System.out.println("Media MinTime: "+d);
					
					seed++;

				}
				// fine degli N run
				double m;
				System.out.println();
				
				System.out.println("Media totale MaxNode: "+mean(maxNode));
				System.out.println("Media totale MinNode: "+mean(minNode));
				m= ( mean(maxNode)+mean(minNode) ) /2.0;
				System.out.println("Media totale *Node: "+m);
				nodiEspansi.print("\t"+ m);
				
				System.out.println("Media totale MaxTime: "+mean(maxTime));
				System.out.println("Media totale MinTime: "+mean(minTime));
				m= ( mean(maxTime)+mean(minTime) ) /2.0;
				System.out.println("Media totale *Time: "+m);
				tempoEsecuzione.print("\t"+ m);

				System.out.println();
			}

			nodiEspansi.println();
			tempoEsecuzione.println();
			
		}		

		nodiEspansi.close();
		tempoEsecuzione.close();
	}
	

	private static void sfidaEuristiche() throws BadMoveException, FileNotFoundException {
		
		PrintStream sfidaEuristiche= new PrintStream("sfida_euristiche");
		
		Search.setSilentDebug(true); // <-------- output classe Search
		Search.setSilent(true); // <-------- output classe Search
		Search.DISABLE_PROBING_AB= false;
		Search.DISABLE_PROBING_CUTOFF= false;
		Search.FAIL_SOFT= false; // fail hard
		
		System.out.println("DISABLE_PROBING_AB="+Search.DISABLE_PROBING_AB);
		System.out.println("DISABLE_PROBING_CUTOFF="+Search.DISABLE_PROBING_CUTOFF);
		System.out.println("FAIL_SOFT="+Search.FAIL_SOFT);
		System.out.println();
		
		
		Vector<Boolean> blackWins= new Vector<Boolean>();
		
		final int maxDepth= 3;
		final SearchType type= SearchType.STAR2;
		
		ComputerPlayer blackPlayers[]= {
				new Hit1(CheckerColor.BLACK, maxDepth, type),
				new Hit2(CheckerColor.BLACK, maxDepth, type),
				new Door(CheckerColor.BLACK, maxDepth, type),
				new DoorHit1(CheckerColor.BLACK, maxDepth, type),
				new DoorHit2(CheckerColor.BLACK, maxDepth, type),
				new Random(CheckerColor.BLACK, maxDepth, type),
		};
		
		ComputerPlayer whitePlayers[]= {
				new Hit1(CheckerColor.WHITE, maxDepth, type),
				new Hit2(CheckerColor.WHITE, maxDepth, type),
				new Door(CheckerColor.WHITE, maxDepth, type),
				new DoorHit1(CheckerColor.WHITE, maxDepth, type),
				new DoorHit2(CheckerColor.WHITE, maxDepth, type),
				new Random(CheckerColor.WHITE, maxDepth, type),
		};
		
		String nomiStrategie[]= {"Hit1", "Hit2", "Door", "DoorHit1", "DoorHit2", "Random"};
		
		sfidaEuristiche.println("\tHit1\tHit2\tDoor\tDoorHit1\tDoorHit2\tRandom");
		
		// il giocatore nero e' per riga
		for (int b=0; b<blackPlayers.length; b++) {
			ComputerPlayer black= blackPlayers[b];
			
			sfidaEuristiche.print(nomiStrategie[b]);
			
			// il giocatore bianco e' per colonna
			for (int w=0; w<b; w++) {
				ComputerPlayer white= whitePlayers[w];
				
				blackWins.clear();
				
				System.out.println("+ + + Nuova configurazione");
				System.out.println(nomiStrategie[b]+"-BLACK" + " VS " + nomiStrategie[w]+"-WHITE");
				System.out.println();
				
				// ciclo sul numero dei run
				int seed= INITIAL_SEED;
				for (int k=0; k<NUM_RUNS; k++) {

					System.out.println("seed="+seed);
					
					// esegue l'intera partita!!!
					boolean win1= playMatch(black, white, seed);
					blackWins.add( win1 );
					System.out.println("Vittoria BLACK (andata)? " + win1);
					
					// inverto le posizioni
					boolean win2= playMatch(white, black, seed);
					blackWins.add( win2 );
					System.out.println("Vittoria BLACK (ritorno)? " + win2);
					
					if (win1!=win2) {
						System.out.println("Andata DIVERSO da ritorno");
					} else {
						System.out.println("Andata uguale a ritorno");
					}
					
					seed++;

				}
				// fine degli N run
				System.out.println();
				double m= meanBool(blackWins);
				System.out.println("Media vittorie per BLACK: "+m);
				sfidaEuristiche.print("\t"+ m);
				System.out.println();

			}
			
			sfidaEuristiche.println();
		}
		
		sfidaEuristiche.close();
	}
	

	public enum StrategyType {HIT1, HIT2, DOOR, DOORHIT1, DOORHIT2, RANDOM};

	private static void sfidaProfondita() throws BadMoveException, FileNotFoundException {
		
		PrintStream sfidaProfondita= new PrintStream("sfida_profondita");
		
		Search.setSilentDebug(true); // <-------- output classe Search
		Search.setSilent(true); // <-------- output classe Search
		Search.DISABLE_PROBING_AB= false;
		Search.DISABLE_PROBING_CUTOFF= false;
		Search.FAIL_SOFT= false; // fail hard
		Search.STAR2= true; // uso star2
		
		System.out.println("DISABLE_PROBING_AB="+Search.DISABLE_PROBING_AB);
		System.out.println("DISABLE_PROBING_CUTOFF="+Search.DISABLE_PROBING_CUTOFF);
		System.out.println("FAIL_SOFT="+Search.FAIL_SOFT);
		System.out.println("STAR2="+Search.STAR2);
		System.out.println();
		
		Vector<Boolean> blackWins= new Vector<Boolean>();

		StrategyType arrTypes[]= {StrategyType.HIT1, StrategyType.HIT2, StrategyType.DOOR, StrategyType.DOORHIT1, StrategyType.DOORHIT2};
		
		final int maxDepth= 5;
		for (StrategyType type : arrTypes) {
			
			sfidaProfondita.println("Strategia: "+type);
			sfidaProfondita.println("\t1\t3\t5");
			
			// ciclo sulla profondita' (giocatore BLACK - riga)
			for (int depthBlack=1; depthBlack<=maxDepth; depthBlack+=2) {

				sfidaProfondita.print(depthBlack);
				
				// ciclo sulla profondita' (giocatore WHITE - colonna)
				for (int depthWhite=1; depthWhite<depthBlack; depthWhite+=2) {

					System.out.println("+ + + Nuova configurazione");
					System.out.println("Strategia: "+type);
					System.out.println("BLACK (depth="+depthBlack+")" + " VS " + "WHITE (depth="+depthWhite+")");
					System.out.println();
					
					blackWins.clear();
					
					// ciclo sul numero dei run
					int seed= INITIAL_SEED;
					for (int k=0; k<NUM_RUNS; k++) {
						ComputerPlayer black;
						ComputerPlayer white;

						switch (type) {
						case HIT1:
							black= new Hit1(CheckerColor.BLACK, depthBlack, SearchType.STAR2);
							white= new Hit1(CheckerColor.WHITE, depthWhite, SearchType.STAR2);
							break;
							
						case HIT2:
							black= new Hit2(CheckerColor.BLACK, depthBlack, SearchType.STAR2);
							white= new Hit2(CheckerColor.WHITE, depthWhite, SearchType.STAR2);
							break;
							
						case DOOR:
							black= new Door(CheckerColor.BLACK, depthBlack, SearchType.STAR2);
							white= new Door(CheckerColor.WHITE, depthWhite, SearchType.STAR2);
							break;
							
						case DOORHIT1:
							black= new DoorHit1(CheckerColor.BLACK, depthBlack, SearchType.STAR2);
							white= new DoorHit1(CheckerColor.WHITE, depthWhite, SearchType.STAR2);
							break;
							
						case DOORHIT2:
							black= new DoorHit2(CheckerColor.BLACK, depthBlack, SearchType.STAR2);
							white= new DoorHit2(CheckerColor.WHITE, depthWhite, SearchType.STAR2);
							break;
							
						default:
							throw new RuntimeException("Strategia non supportata");
						}

						System.out.println("seed="+seed);

						// esegue l'intera partita!!!
						boolean win1= playMatch(black, white, seed);
						blackWins.add( win1 );
						System.out.println("Vittoria BLACK (andata)? " + win1);
						
						// inverto le posizioni
						boolean win2= playMatch(white, black, seed);
						blackWins.add( win2 );
						System.out.println("Vittoria BLACK (ritorno)? " + win2);
						
						if (win1!=win2) {
							System.out.println("Andata DIVERSO da ritorno");
						} else {
							System.out.println("Andata uguale a ritorno");
						}
						
						seed++;

					}
					// fine degli N run

					System.out.println();
					double m= meanBool(blackWins);
					System.out.println("Media vittorie per BLACK: "+m);
					sfidaProfondita.print("\t"+ m);
					System.out.println();
					
				}
				
				sfidaProfondita.println();
			}
			
			sfidaProfondita.println();
			
		}
		

		sfidaProfondita.close();
	}
	
	/**
	 * 
	 * 
	 * @param player1
	 * @param player2
	 * @param seed
	 * @return true se vince BLACK (indipendentemente che sia player1 o player2), false altrimenti
	 * @throws BadMoveException
	 */
	private static boolean playMatch(ComputerPlayer player1, ComputerPlayer player2, int seed) throws BadMoveException {
		
		Backgammon bg= new Backgammon(player1, player2, seed); // al costruttore non interessa chi sia BLACK o WHITE
		bg.setSilent(true); // <------------- output classe Backgammon

		ComputerPlayer turn= tieBreak(player1, player2, bg); // qui dentro richiamo Search.resetStats()
		boolean win= false;
		
		while (!win) {	
			System.out.println("Turno: "+bg.getTurnNum()+" - "+bg.getTurnPlayer().getColor());
			
			// lancio i dadi
			int dice1= bg.rollDice();
			int dice2= bg.rollDice();
			bg.setRoll(dice1, dice2);
			
			// cerco la mossa
			Move move= (Move) turn.bestMove(bg);
			
			// eseguo la mossa
			win= bg.move(move);
			
			// cambio giocatore
			if (!win) {
				CheckerColor color= bg.tooglePlayerTurn();
				if (color.isBlack()) {
					if (player1.getColor().isBlack()) turn= player1;
					else turn= player2;
				} else {
					if (player1.getColor().isWhite()) turn= player1;
					else turn= player2;
				}
			}
		}
		return turn.getColor().isBlack();
	}

	/**
	 * player1 tira per primo il dado e poi tira player2.
	 * @param player1
	 * @param player2
	 * @param bg
	 * @return il player che ha vinto lo spareggio
	 */
	private static ComputerPlayer tieBreak(ComputerPlayer player1, ComputerPlayer player2, Backgammon bg) {
		
		int dice1, dice2;
		
		// dadi dello spareggio
		dice1= bg.rollDice();
		dice2= bg.rollDice();
		
		if(dice1==dice2){
			// stesso valore, lo spareggio deve essere rieseguito
			return tieBreak(player1, player2, bg); // nessuna iterazione ma solo ricorsione!!!
		}

		ComputerPlayer first= null;

		if(dice1 < dice2){
			first=player2; 
		} else if (dice2 < dice1){
			first=player1;
		}

		// imposto chi inizia il turno
		bg.setPlayerTurn(first.getColor());

		Search.resetStats();
		
		return first;
	}
	
	
	public static double mean(List<Double> arr) {

		double sum = 0;
		for (double l : arr) {
			sum += l;
		}

		return (sum / (double)arr.size());
	}
	public static double meanBool(List<Boolean> arr) {

		double sum = 0;
		for (boolean l : arr) {
			sum += l ? 1.0 : 0.0;
		}

		return (sum / (double)arr.size());
	}
}
